#include "pch.h"
#include "GLImageBeautyComplexionFilter.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "filesystem.h"



#define VERTEX_POS_INDX  0
#define TEXTURE_POS_INDX 1
GLImageBeautyComplexionFilter::GLImageBeautyComplexionFilter()
{
	m_VaoIds[0] = GL_NONE;
	m_VboIds[0] = GL_NONE;

	m_ImageTextureId = GL_NONE;
	m_FboTextureId = GL_NONE;
	m_SamplerLoc = GL_NONE;
	m_FboId = GL_NONE;
	m_FboProgramObj = GL_NONE;
	m_FboVertexShader = GL_NONE;
	m_FboFragmentShader = GL_NONE;
	m_FboSamplerLoc = GL_NONE;

	paramsLoc = GL_NONE;
	brightnessLoc = GL_NONE;
	singleStepOffsetLoc = GL_NONE;
	texelWidthLoc = GL_NONE;
	texelHeightLoc = GL_NONE;

	texelWidthOffset = texelHeightOffset = 2;
	msetSmoothLeve = 3.0f;//�˲���
    toneLevel = -0.5f; 
    beautyLevel =1.2f; 
    brightLevel =0.47f;
}


GLImageBeautyComplexionFilter::~GLImageBeautyComplexionFilter()
{
}

void GLImageBeautyComplexionFilter::Init(int w, int h)
{
	//��������
	GLfloat vVertices[] = {
			-1.0f, -1.0f, 0.0f,
			 1.0f, -1.0f, 0.0f,
			-1.0f,  1.0f, 0.0f,
			 1.0f,  1.0f, 0.0f,
	};

	//������������
	GLfloat vTexCoors[] = {
			0.0f, 1.0f,
			1.0f, 1.0f,
			0.0f, 0.0f,
			1.0f, 0.0f,
	};

	//fbo ����������������������ͬ��ԭ��λ�����½�
	GLfloat vFboTexCoors[] = {
			0.0f, 0.0f,
			1.0f, 0.0f,
			0.0f, 1.0f,
			1.0f, 1.0f,
	};

	GLushort indices[] = { 0, 1, 2, 1, 3, 2 };



	// ��������������ͨ��Ⱦ����ɫ������
	//m_ProgramObj = OpenGLUtils::CreateProgram("shader/ch.vs", "shader/ch.fs", m_VertexShader, m_FragmentShader);

	// ������������������Ⱦ����ɫ������
	m_FboProgramObj = OpenGLUtils::CreateProgram("shader/ch.vs", "shader/smooth_white_skin.fs", m_FboVertexShader, m_FboFragmentShader);
	//m_ProgramObj == GL_NONE || 
	if (m_FboProgramObj == GL_NONE)
	{
		
		return;
	}

	//m_SamplerLoc = glGetUniformLocation(m_ProgramObj, "inputTexture");//��ͨ��Ⱦ
	m_FboSamplerLoc = glGetUniformLocation(m_FboProgramObj, "inputTexture");//fbo
	//�����ز�

	paramsLoc = glGetUniformLocation(m_FboProgramObj, "params");
	brightnessLoc = glGetUniformLocation(m_FboProgramObj, "brightness");
	singleStepOffsetLoc = glGetUniformLocation(m_FboProgramObj, "singleStepOffset");
	texelWidthLoc = glGetUniformLocation(m_FboProgramObj, "texelWidthOffset");
	texelHeightLoc = glGetUniformLocation(m_FboProgramObj, "texelHeightOffset");



	// ���� VBO �����ض������ݺ���������
	// Generate VBO Ids and load the VBOs with data
	glGenBuffers(4, m_VboIds);
	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), vVertices, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vTexCoors), vTexCoors, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[2]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vFboTexCoors), vFboTexCoors, GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[3]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	GO_CHECK_GL_ERROR();

	// ���� 2 �� VAO��һ��������ͨ��Ⱦ����һ������������Ⱦ
	// Generate VAO Ids
	glGenVertexArrays(1, m_VaoIds);
	// ��ʼ��������ͨ��Ⱦ�� VAO
	// Normal rendering VAO
	/*glBindVertexArray(m_VaoIds[0]);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
	glEnableVertexAttribArray(VERTEX_POS_INDX);
	glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (const void *)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
	glEnableVertexAttribArray(TEXTURE_POS_INDX);
	glVertexAttribPointer(TEXTURE_POS_INDX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (const void *)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[3]);
	//GO_CHECK_GL_ERROR();
	glBindVertexArray(GL_NONE);*/


	// ��ʼ������������Ⱦ�� VAO
	// FBO off screen rendering VAO
	glBindVertexArray(m_VaoIds[0]);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
	glEnableVertexAttribArray(VERTEX_POS_INDX);
	glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (const void*)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
	glEnableVertexAttribArray(TEXTURE_POS_INDX);
	glVertexAttribPointer(TEXTURE_POS_INDX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (const void*)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[3]);
	//GO_CHECK_GL_ERROR();
	glBindVertexArray(GL_NONE);

	// ��������ʼ��ͼ������
	glGenTextures(1, &m_ImageTextureId);
	glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	width = w;
	height = h;
	setTexelSize(width, height);//��������size
	setTexelOffset(msetSmoothLeve); //ĥƤ��
	glBindTexture(GL_TEXTURE_2D, GL_NONE);
	GO_CHECK_GL_ERROR();

	//mGrayTexture = createTextureFromImage("texture/skin_gray.png", 0);
	//mLookupTexture = createTextureFromImage("texture/skin_lookup.png", 1);

	if (!CreateFrameBufferObj())
	{
		printf("FBOSample::Init CreateFrameBufferObj fail");
		return;
	}
	// load image, create texture and generate mipmaps
}

/**
	 * �����̶�
	 * @param level 0 ~ 1.0f
	 */
void GLImageBeautyComplexionFilter::setComplexionLevel(float setSoomthValue, float BeautySkin, float Bright) {
	
	msetSoomthValue = setSoomthValue;  //ĥƤֵ
	mBeautySkin = BeautySkin;    //����
	mBright = Bright;        //����
}


void GLImageBeautyComplexionFilter::onDrawFrameBegin() {
	
	msetSoomthValue = msetSoomthValue;
	setToneLevel(msetSoomthValue);  //ĥƤֵ
	setBeautyLevel(mBeautySkin);    //����
	setBrightLevel(mBright);        //����
}

Mat GLImageBeautyComplexionFilter::Draw( Mat frame)
{
	// ������Ⱦ
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glViewport(0, 0, width, height);
	
	// Do FBO off screen rendering
	glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
	glUseProgram(m_FboProgramObj);
	glBindVertexArray(m_VaoIds[0]);
	glActiveTexture(GL_TEXTURE0);
	
	glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.cols, frame.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, frame.data);
	glGenerateMipmap(GL_TEXTURE_2D);
	glUniform1i(m_FboSamplerLoc, 0);

	onDrawFrameBegin();

	GO_CHECK_GL_ERROR();
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
	//GO_CHECK_GL_ERROR();
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);


	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	 return GetOcvImgFromOglImg(m_FboTextureId);
	//Mat img(2 * height, 2 * width, CV_8UC3);
	//glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);
	//glPixelStorei(GL_PACK_ROW_LENGTH, img.step / img.elemSize()); // ��䲻�Ӻ���Ҳû���⣿
	//glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);
	//Mat flipped;
	//flip(img, flipped, 0);
	
	
	// ��ͨ��Ⱦ
	// Do normal rendering
	/*
	glViewport(0, 0, img.cols, img.rows);
	glUseProgram(m_ProgramObj);
	//GO_CHECK_GL_ERROR();
	glBindVertexArray(m_VaoIds[0]);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
	glUniform1i(m_SamplerLoc, 0);
	//GO_CHECK_GL_ERROR();
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
	//GO_CHECK_GL_ERROR();
	glBindTexture(GL_TEXTURE_2D, GL_NONE);
	glBindVertexArray(GL_NONE);
	return flipped;*/

}

void GLImageBeautyComplexionFilter::Destroy()
{
	if (m_ProgramObj)
	{
		glDeleteProgram(m_ProgramObj);
	}

	if (m_FboProgramObj)
	{
		glDeleteProgram(m_FboProgramObj);
	}

	if (m_ImageTextureId)
	{
		glDeleteTextures(1, &m_ImageTextureId);
	}

	if (m_FboTextureId)
	{
		glDeleteTextures(1, &m_FboTextureId);
	}

	if (m_VboIds[0])
	{
		glDeleteBuffers(4, m_VboIds);
	}

	if (m_VaoIds[0])
	{
		glDeleteVertexArrays(2, m_VaoIds);
	}

	if (m_FboId)
	{
		glDeleteFramebuffers(1, &m_FboId);
	}

}

bool GLImageBeautyComplexionFilter::CreateFrameBufferObj()
{
	// ��������ʼ�� FBO ����
	glGenTextures(1, &m_FboTextureId);
	glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, GL_NONE);

	// ��������ʼ�� FBO
	glGenFramebuffers(1, &m_FboId);
	glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
	glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_FboTextureId, 0);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		printf("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE /n");
		return false;
	}
	glBindTexture(GL_TEXTURE_2D, GL_NONE);
	glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
	return true;

}




/*
texelWidthOffset = texelHeightOffset = 2;
	toneLevel = -0.5f;
	beautyLevel =1.2f;
	brightLevel =0.47f;
*/
void GLImageBeautyComplexionFilter::setTexelOffset(float texelOffset) {
	texelWidthOffset = texelHeightOffset = texelOffset;

	setFloat(texelWidthLoc, texelOffset / width);
	setFloat(texelHeightLoc, texelOffset / height);
}

void GLImageBeautyComplexionFilter::setToneLevel(float toneLeve) {
	toneLevel = toneLeve;
	setParams(beautyLevel, toneLevel);
}

void GLImageBeautyComplexionFilter::setBeautyLevel(float beautyLeve) {
	beautyLevel = beautyLeve;
	setParams(beautyLevel, toneLevel);
}

void GLImageBeautyComplexionFilter::setBrightLevel(float brightLevel) {
	brightLevel = brightLevel;
	setFloat(brightnessLoc, 0.6f * (-0.5f + brightLevel));
}

void GLImageBeautyComplexionFilter::setParams(float beauty, float tone) {
	beautyLevel = beauty;
	toneLevel = tone;
	float vector[4] = {};
	vector[0] = 1.0f - 0.6f * beauty;
	vector[1] = 1.0f - 0.3f * beauty;
	vector[2] = 0.1f + 0.3f * tone;
	vector[3] = 0.1f + 0.3f * tone;
	//setFloatVec4(paramsLoc, vector);
	setVec4(paramsLoc, vector[0], vector[1], vector[2], vector[3]);
}

void GLImageBeautyComplexionFilter::setTexelSize(float w, float h) {
	float vect2[2] = {};
	vect2[0] = 2.0f / w;
	vect2[1] = 2.0f / h;
	setFloatVec2(singleStepOffsetLoc, vect2);
}

void GLImageBeautyComplexionFilter::setFloat(int location, float floatValue) {
	glUniform1f(location, floatValue);
}
void GLImageBeautyComplexionFilter::setFloatVec2(int location, float* arrayValue) {
	glUniform2fv(location, 1, arrayValue);
}

void GLImageBeautyComplexionFilter::setFloatVec3(int location, float* arrayValue) {
	glUniform3fv(location, 1, arrayValue);
}

void GLImageBeautyComplexionFilter::setFloatVec4(int location, float* arrayValue) {
	glUniform4fv(location, 1, arrayValue);
}

void GLImageBeautyComplexionFilter::setVec4(GLuint programId, float x, float y, float z, float w) {
	glUniform4f(programId, x, y, z, w);
}

Mat GLImageBeautyComplexionFilter::GetOcvImgFromOglImg(GLuint ogl_texture_id)
{
	glBindTexture(GL_TEXTURE_2D, ogl_texture_id);
	GLenum gl_texture_width, gl_texture_height;

	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint*)&gl_texture_width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint*)&gl_texture_height);

	unsigned char* gl_texture_bytes = (unsigned char*)malloc(sizeof(unsigned char)*gl_texture_width*gl_texture_height * 3);
	glGetTexImage(GL_TEXTURE_2D, 0 /* mipmap level */, GL_BGR, GL_UNSIGNED_BYTE, gl_texture_bytes);
	Mat flipped;
	Mat img(gl_texture_height, gl_texture_width, CV_8UC3, gl_texture_bytes);
	flip(img, flipped, 0);
	return flipped;
}